UltraMID Programmer's Reference Guide
Revision 1.00.03
22 December 1993
Forte Technologies
1555 East Henrietta Rd.
Rochester, N.Y. 14623
FAX (716) 292-6353
BBS (716) 427-2921
NOTICE
The information contained in this manual is believed to be
correct. The manual is subject to change without notice and
does not represent a commitment on the part of FORTE or
Advanced Gravis.
Neither FORTE nor Advanced Gravis make a warranty of any
kind with regard to this material, including, but not
limited to, the implied warranties of merchantability and
fitness for a particular purpose. Neither FORTE nor
Advanced Gravis shall be liable for errors contained herein
or for incidental or consequential damages in connection
with the furnishing, performance or use of this material.
This document contains proprietary information which is
protected by copyright. This manual is Copyright (C)
1992,1993 by FORTE and Advanced Gravis. All rights are
reserved. No part of this document may be reproduced,
transmitted, transcribed, stored in a retrieval system, or
translated into any human or computer language, in any form
or by any means; electronic, mechanical, magnetic, optical,
chemical, manual or otherwise, without the expressed written
Forte Technologies
UltraMID Programmer's Reference Guide Page 2
Copyright Notice
permission of FORTE and Advanced Gravis.
Any copying, duplication, selling, or otherwise distributing
the program or support files described in this manual, other
than for the limited purposes of system backup and loading
the program into the computer as part of executing the
program, is a violation of the software license agreement
and the law. Willful violation of the copyright law of the
United States can result in statutory damages of up to
$50,000 in addition to actual damages, plus criminal
penalties of imprisonment for up to one year and/or a
$10,000 fine.
Forte Technologies
UltraMID Programmer's Reference Guide Page 3
Chapter 1 - Introduction
UltraMID is a TSR program that has approximately 20
functions to support general midi music synthesis, smart
patch loading, and DMA-driven digital audio playback.
UltraMID is an alternative to the low-level SDK. It
provides very high level access to the UltraSound card so
that a programmer can write software to synthesize General
Midi music with minimal effort. In fact, the UltraMID TSR
is easier to use than other popular midi processors.
UltraMID also makes it very easy for programmers to produce
digital audio sound effects with a minimal amount of
additional code and memory. This document is a technical
document with the following information: A brief overview of
the UltraSound card; An overview of how the digital audio
section works; An overview of how the midi section works;
And Complete documentation on each of the functions in
UltraMID.
Also included with this documentation are some
programming examples. The following files should be of some
interest to you:
* umwav.c: A .WAV file player in C.
* aumwav.asm: A .WAV file player written in assembly.
* ummidi.c: a .MID file player written in C. This
example consists of 4 .c files. The file totsr.c
contains the UltraMID interface. This example is
based on the source code used for playmidi.exe which
comes with the UltraSound card.
* umuart.asm: A program that receives data from the
UltraSound UART and plays the midi data.
____________________________________________________________
Forte Technologies
UltraMID Programmer's Reference Guide Page 4
Chapter 2 - The UltraSound audio card
The UltraSound audio card
************************************************************
Block Diagram:
+---------------------------------------------------------------------+
| +---------------+ |
| |MEMORY MEMORY| Amplified Out (st) o
| | | +-------+ Line Out (stereo) o
| |MEMORY MEMORY|----| GF1 | /
| | | | ASIC |---I/O Ports/DMA |
| |MEMORY MEMORY| +-------+ / \ Midi/Joystick port |
| | | / \ |
| |MEMORY MEMORY| / \ \
| +---------------+ / \ Mic input (stereo) o
| (Memory hold all sounds) / \ Line input (stereo) o
|_________________________ / ___\ ___|
|___16bit___________| |__8bit_____________| |
|
The heart of the UltraSound card is based on an ASIC
chip called the GF1. The GF1 has no general purpose
processing capabilites. It is "hardwired" pipelined
processor which performs a very specific task. Simply put,
it interpolates sound data, applies attenuation to produce
pan effects and volume, sums data, and produces two digital
signals which are passed on to the analog circuitry to
produce sound. The low-level SDK provides routines to write
to all of the registers in the GF1 chip and to control each
of these operations. UltraMID requires no knowledge of the
GF1 internal process or registers, but it is useful to
understand a little bit about how this works so that you can
visualize what's happening "underneath the hood."
The GF1 has a single speed at which it operates
internally. Unlike other digital processors which can play
sound data at varying speeds, the GF1 uses a completely
different method for frequency control. If a sample is
recorded at 44.1Khz, the GF1 plays that sample by sending
each byte in order to the DACs. If you want to play back
the sound at 22Khz, the GF1 will interpolate values between
the sound data points in memory to achieve the slower rate.
Forte Technologies
UltraMID Programmer's Reference Guide Page 5
Chapter 2 - The UltraSound audio card
The interpolated sample values are then multipled by values
in the pan register to produce to sample values, one for the
left ear, and one for the right. The GF1 then applies
volume attenuation based on the value of the volume
register. The two values are finally converted to and
analog signal.
The GF1 can play up to 32 voices by repeating the steps
listed above. The GF1 has 32 seperate sets of registers to
control volume, pan, and position. UltraMID uses 24 voices.
Each voice is independant from the other voices. They can
point to different memory, play at different rates, volumes,
pan positions, and data types. The voices can play data
forward, backward, loop forward, loop backward, loop
bidirectionally. The volume of each voice can be modified
in a similar fasion. Given a start volume, end volume, and
a rate, the GF1 can automatically make the volume louder,
softer, or loop from soft to loud, loud to soft, or
bidirectionally. All of these features are used in the midi
engine, however, the UltraMID programmer does not have to
write any additional code to make use of these features.
Forte Technologies
UltraMID Programmer's Reference Guide Page 6
Chapter 3 - Voice Allocation using UltraMID
Voice allocation using UltraMID.
************************************************************
The UltraSound card can have up to 32 mono sounds
playing at one time, or 16 stereo, or combinations.
UltraMID runs the UltraSound card in a mode where 24 of
those voices are available. UltraMID will allow you to
allocate voices for digital audio using a prioritized
scheme. A priority of 0 is the highest priority. If you
request a voice with a 0 priority, you will get one, and it
will remain yours until you give it up. Any other priority
means that your voice can get stolen from you if another
request is made at a higher priority.
Voices are also aged. If you allocate 25 voices with a
priority of 1, then the 25th voice will be obtained by
stealing the first voice that was allocated. This is
because the first voice has been allocated the longest and
because UltraMID uses 24 voices. If a voice was allocated
as part of a group of voices, e.g. a stereo sample, and the
voice gets stolen, then the other voice will get stolen
also.
MIDI voices are allocated dynamically and
automatically. Each voice is allocated initially with a
priority of 1. The voices priority is lowered as it plays
through its envelope. So, a midi note that is fading out
has a better chance of getting stolen then a new note that
is sustaining. For example, if notes A and B start at the
same time, and note A sustains while note B starts to taper
off, then note B will be stolen before note A.
N |
O B| **********----------
T A| ********************
E +---+---------------------------------------------------------------
Time --> ^- start release for note B.
Voices for digital audio playback are assigned a priority by
your application.
Forte Technologies
UltraMID Programmer's Reference Guide Page 7
Chapter 4 - Digital Audio playback with UltraMID.
Digital Audio playback with UltraMID.
************************************************************
The UltraSound card has its own DRAM seperate from that
of the PC which it uses to store data for playback. Each of
the voices points to memory locations on the UltraSound
card. The UltraSound adds up all of the data at these
memory locations and sends it to the left and right stereo
outputs for playback. In order to play an 8K sample, for
example, you must allocate memory on the card, transfer the
sound data to this memory, and start the voice. You must
also set the initial pan position (balance), volume, sample
rate, data type (8, 16, signed, unsigned, stereo, mono). A
question that might come to your mind is, what if I want to
play a sample bigger than 1Mb, let's say, 2Mb. UltraMID was
designed especially to handle the case where you want to
play large samples with limited memory. UltraMID will allow
you to allocate a small buffer, lets say 8K for example, and
then split up your 2MB sample into many smaller chunks.
| |
|-------------|-------------|
0K 4K 8K
Initially, there is no data in the 8K buffer. You start by
reading 4K of data from the PC hard disk, and sending it to
UltraMID.
|** S |
|^------------|-------------|
0K 4K 8K
UltraMID will start to DMA the data (*) into the UltraSound
card at the fastest rate supported by the UltraSound which
can be up to 680Kb/s. At the same time, the UltraMID starts
the voice playing (^).
A moment later:
Forte Technologies
UltraMID Programmer's Reference Guide Page 8
Chapter 4 - Digital Audio playback with UltraMID.
|*************S |
|--^----------|-------------|
0K 4K 8K
Notice how the DMA transfer has completed much earlier than
the voice. A 44.1Khz voice is 15 times slower than the
optimal DMA transfer rate. At this point UltraMID will call
your application through a far address that you provide and
ask you for MORE_DATA. If your application has another
buffer ready, it will return it to UltraMID, and the next
DMA transfer will occur.
|***************************S
|---------^---|-------------|
0K 4K 8K
As time goes by, the voice will finally reach your original
first 4K mark and then UltraMID will call you back telling
you that it no longer needs access to that buffer
(BUFFER_DONE).
| **************S
|-------------^-------------|
0K 4K 8K
Immediately following the BUFFER_DONE callback, you will
receive a MORE_DATA callback. If you respond with more
data, than things will look like this:
|*************S*************Loop
|-------------|^------------|
0K 4K 8K
In this example your application passed UltraMID 4K buffers,
however, UltraMID can take any size buffer and it will
manage the data internally in the 8K buffer in the same
fashion for you. If you had 256K of data on the PC that you
wanted to play, then you would pass the whole thing to
UltraMID. When UltraMID got to the last 4K of the data, it
would make a callback to you asking for MORE_DATA. You
could than respond with another buffer of any size.
Recommendations:
* The UltraSound card can only DMA from PC memory into
32 byte boundaries in the card. If you present a
buffer that is not a multiple of 32 bytes, then
UltraMID will have to do direct I/O to the card
which takes more CPU time than a typical DMA. If
you make sure your buffer sizes are a multiple of 32
bytes, than the polled I/O to the card will be
avoided.
Forte Technologies
UltraMID Programmer's Reference Guide Page 9
Chapter 4 - Digital Audio playback with UltraMID.
* If the sample you want to play is not entirely in PC
memory, but you are reading it from disk, then there
are several things to be aware of. All hard disks
have a higher transfer rate then the fastest sound
you will play on your UltraSound. However, the
transfer rate is not the only variable to pay
attention to. It takes a relatively long time for
the data on the spinning disk to find its way
underneath the read/write head of the drive. It
takes much longer than that for the disk arm to move
from track to track. It is sometimes better to use
many small buffers than it is to use 2 very large
buffers. In the .WAV player example (aumwav.asm or
umwav.c), the number and size of the buffers can be
changed, try adjusting the numbers and try playing
different .WAV files on different drives. (The
assembly version must have a buffer size which is a
power of 2).
There is an example program that you should have received
with this document that will play a .WAV formatted file
using UltraMID. If you are interested in the C version,
look at umwav.c. If you are interested in the assembly
version look at aumwav.asm. Neither example requires a C
library. You should be able to compile the examples use the
Borland C++ 3.10 compiler or the Borland assembler (tasm).
The structure shown below is used to start a digital
audio playback. It can be found in the files ultramid.h
(c), ultramid.inc (masm), and ultmidi.inc (tasm ideal).
um_sound_struct struc
um_sound_data label dword
um_sound_data_off dw ?
um_sound_data_seg dw ?
um_stereo_mem label dword
um_stereo_mem_off dw ?
um_stereo_mem_seg dw ?
um_sound_len dd ?
um_gf1mem dd ?
um_pan db ?
um_volume dw ?
um_sample_rate dw ?
label um_voice word
um_priority dw ?
um_data_type db ?
um_callback_addr label dword
um_callback_addr_off dw ?
um_callback_addr_seg dw ?
um_sound_struct ENDS
Forte Technologies
UltraMID Programmer's Reference Guide Page 10
Chapter 4 - Digital Audio playback with UltraMID.
Here is a brief description of each of the fields of this
structure:
* um_sound_data label dword
* um_sound_data_off dw ?
* um_sound_data_seg dw ?
A far pointer to your sound data to start playback.
There is no requirements about alignment of this data
in PC memory. You can put it wherever you want, and it
can even cross a 64K segment boundary.
* um_stereo_mem label dword
* um_stereo_mem_off dw ?
* um_stereo_mem_seg dw ?
The UltraSound card cannot play interleaved stereo data
without also jumping through hoops and doing magic
tricks when running with 24 active voices. UltraMID
will split up interleaved stereo data into smaller
buffers before it DMA's it into the UltraSound card.
This field should be a far pointer to PC memory which
contains as much memory as 1/4 of the buffer used on
the UltraSound card. In the above example, the
UltraSound buffer was divided in half for mono
playback. In a stereo situation, the buffer is divided
4 ways. UltraMID will use this memory for DMA
transfers. The example used an 8K buffer, so
um_stereo_mem should point to a 2K PC buffer for that
example.
|left |right |
|------|------|------|------|
0K 2K 4K 6K 8K
* um_sound_len dd ?
The length of the sample to be played in bytes. Be
careful not to split up a sample when dividing your
sound data into multiple buffers. For example, one
16-bit-stereo sample is 4 bytes. Therefore
um_sound_len should be a multiple of 4 for that data
type.
* um_gf1mem dd ?
The address of the memory allocated on the UltraSound
card. This is the address where data will be
transferred for playback. You can allocate memory with
the function TSR_ALLOCATE_MEMORY, and free it with
TSR_FREE_MEMORY.
* um_pan db ?
The balance control for the voice. 0 is all the way to
the left, and 15 is all the way to the right.
* um_volume dw ?
A value from 0 to 4095. This is a logarithmic volume
scale. To (approximately) convert from a linear scale
Forte Technologies
UltraMID Programmer's Reference Guide Page 11
Chapter 4 - Digital Audio playback with UltraMID.
(0 - 65535) to a log scale:
/ 256 * log(65536/linear_volume) \
log_volume = 4096 - | ------------------------------- |
\ log(2.0) /
or from a log scale (0 - 4095) to a linear scale (0 - 65536):
65536
linear_volume = ---------------------------------------------------
(4096-log_volume) * ( ln(2) / 256 )
e
The file vol.inc has a short table which can be used to
implement a linear volume scale.
* um_sample_rate dw ?
The sample rate that the sound was recorded at.
* label um_voice word
* um_priority dw ?
On entry to TSR_START_DIGITAL, this contains the
priority of the voice. 0 is the highest priority. If
you use a priority of 0, the voice will remain
allocated until you have completed playback of your
sample. If you use a lower priority, such as 1, then
the voice may be stolen when another request is made to
UltraMID for another voice. Voices are allocated to
turn on midi notes and when calls to TSR_START_DIGITAL
are made. If the operation is successful, the voice
number is returned. Otherwise a -1 is returned. The
voice number is used in following calls to change
volume, pan, or stop the voice.
* um_data_type db ?
um_data_type is a mask containing the following values:
UM_8BIT EQU 1 ; 1 use 8 bit data
; 0 use 16 bit data
UM_PRELOAD EQU 2 ; preload data (not implemented yet)
UM_INVERT_MSB EQU 4 ; invert most significant bit during dma
UM_STEREO EQU 8 ; 1 for stereo data
The GF1 always plays signed data. If you want the GF1
to play unsigned data, then set the UM_INVERT_MSB flag.
This will cause the data to get converted in hardware
during the DMA transfer.
If you are playing 8 bit mono unsigned data, then you
would pass to UltraMID: (UM_8BIT OR UM_INVERT_MSB). If
you are playing 16 bit signed stereo data, then you
would pass to UltraMID: (UM_STEREO). These are the
most common data types.
Forte Technologies
UltraMID Programmer's Reference Guide Page 12
Chapter 4 - Digital Audio playback with UltraMID.
* um_callback_addr label dword
* um_callback_addr_off dw ?
* um_callback_addr_seg dw ?
This should be a far pointer to a routine in your code
to handle callbacks from UltraMID. UltraMID will call
your code to tell you when a buffer has completed
playback, when it needs more data, and when it is done
playing and has deallocated the voice. If you fill
this field with 0:0, then you won't get a callback.
The voice will remain playing until the sample has
completed. If you plan on calling UltraMID with no
callback address, then you should use a non-zero
priority so that voice-stealing can take place.
An example of how digital audio works is in the file
umwav.c (c code) and in aumwav.asm (assembly). This example
will play a .WAV file.
Forte Technologies
UltraMID Programmer's Reference Guide Page 13
Chapter 5 - MIDI playback with UltraMID.
MIDI playback with UltraMID.
************************************************************
UltraMID will accept a general MIDI data stream and
synthesize music from that data. The data is passed to
UltraMID through two functions. If you want to pass
UltraMID a byte at a time, you would call TSR_MIDI_OUT. If
you want to send a string of midi data, then you would call
TSR_MIDI_OUT_STR.
The UltraSound card has no build in patches
(instruments). There is no ROM or FM synthesizer. All
patches must be loaded before you start your MIDI sequence.
The patches can be loaded when UltraMID starts with the -c
parameter, or your application can load patches with little
effort using UltraMID.
UltraMID has two modes of operations. If UltraMID is
started with the "-c" parameter, then a subset of the
general midi instrument set will be downloaded into the
card. The programmer will have to do no additional work to
get sounds into the card. This is probably the most
appropriate mode of operation for people who already have
existing drivers for other cards and can't find a way to
load instruments without changing the other drivers. The
other mode of operation requires that the programmer load
the correct patches before playback starts. UltraMID has
several functions to help load the patches.
TSR_LOAD_MIDI_PATCHES takes a far pointer to the
beginning of a track from a standard midi file and loads all
of the patches that will be needed for that track.
TSR_LOAD_XMIDI_PATCHES takes a far pointer to the
beginning of the event list in an xmidi file and loads all
of the patches that will be needed for that track.
TSR_LOAD_PATCH will load a patch from a program change.
If you know you are going to be doing program changes to 0
(piano 1), then you would call TSR_LOAD_PATCH with 0.
TSR_UNLOAD_PATCH will clear a patch from the
UltraSounds memory.
UltraMID also provides access to the UltraSound's UART
for MIDI input. Your application can receive all of the
MIDI data that reaches the UART by calling a function to set
up a callback routine. Your callback routine will be called
from UltraMID whenever there is MIDI data or an error from
Forte Technologies
UltraMID Programmer's Reference Guide Page 14
Chapter 5 - MIDI playback with UltraMID.
the UART. The function to activate the callback is
TSR_SET_MIDI_CALLBACK. TSR_CLEAR_MIDI_CALLBACK will
disactivate the callback.
Take a look at the ummidi.exe example to see how to
play standard midi files with UltraMID. Ummidi.c is the
main program which calls all of the other parts. midifile.c
reads the midi file into memory. Midiout.c interprets the
midi data and makes calls to totsr.c which contains all of
the calls to UltraMID. Totsr.c also handles the system
timer. The file umuart.c contains an example program that
shows the use of the MIDI UART for MIDI input.
Forte Technologies
UltraMID Programmer's Reference Guide Page 15
Chapter 6 - List of functions supported by UltraMID
************************************************************
TSR_START_DIGITAL equ 0
This function will play continuous digital data of any
length. It supports the following data types:
* 8 bit signed mono data
* 16 bit signed mono data
* 8 bit signed interleaved (l/r) stereo data
* 16 bit signed interleaved (l/r) stereo data
* 8 bit unsigned mono data
* 16 bit unsigned mono data
* 8 bit unsigned interleaved (l/r) stereo data
* 16 bit unsigned interleaved (l/r) stereo data
This function will start a playing digital data by
transferring the data described in the um_sound_struct
into the UltraSound card at the specified address. The
callback address (um_callback_addr, in the
um_sound_struct) should point to a function that you
supply. This function will get for the following
reasons:
1) UM_STOP_SOUND: the voice has been stopped or
stolen, and must no longer be used by your
application. If the voice was stolen, and you
have more data to play, than another voice must
be allocated and started.
2) UM_MORE_DATA: UltraMID needs more data so that
there is a smooth playback without clicks.
This callback does NOT mean that the last
buffer is done playing. It just means UltraMID
Forte Technologies
UltraMID Programmer's Reference Guide Page 16
Chapter 6 - List of functions supported by UltraMID
is ready for the next buffer. When you get
this callback, UltraMID will look at the return
value to figure out what to do next. The list
of return values is described below.
3) UM_BUFFER_DONE: UltraMID is done with a buffer.
This means that a buffer has completed playing,
and you can re-use that PC memory.
When you receive the UM_MORE_DATA callback, you can
take one of the following actions:
* UM_STOP_SOUND: This will tell UltraMID that you
have no more data available and it is OK to
deallocate the voice when the data is done
playing. If you have more data that you want
to play, but it is not available (it is still
on the hard disk), you can use the next option.
You may receive more than one UM_MORE_DATA
request even after you return UM_STOP_SOUND.
UltraMID will stop sending callbacks only after
UltraMID calls YOUR application with
UM_STOP_SOUND.
* UM_PAUSE: This will tell UltraMID to pause
playback. This return value may be ignored
until UltraMID is done playing all of the data
it has shipped to the card. At this point, the
UM_MORE_DATA callback will occur again. If you
return UM_PAUSE again, then it will finally
pause. When eventually you do have more data
to play, you can call the function
TSR_PLAY_NEXT_BUFFER to restart the voice with
new data. UM_PAUSE should only be used to
prevent UltraMID from deallocating the voice.
It is different from the TSR_PAUSE_DIGITAL
command, which pauses playback immediately.
* UM_MORE_DATA: This is the value you should
return if you actually have more data for
UltraMID to start playing.
UltraMID passes your callback routine several
parameters on the stack. If you are using C, you can
use the following prototype:
int far um_callback(int reason,
int voice,
unsigned char far * far *buf,
unsigned long far *size,
unsigned short far *rate
Forte Technologies
UltraMID Programmer's Reference Guide Page 17
Chapter 6 - List of functions supported by UltraMID
If you are using assembly, you can access these
parameters by creating a stack frame: push bp; mov bp,
sp; In large memory model:
* reason:word [bp+6]
* voice:word [bp+8]
* buf:dword ptr [bp+10]
* size:dword ptr [bp+14]
* rate:word ptr [bp+18]
Descriptions of the callback parameters:
* reason: One of the reasons listed above.
* voice: The same number as the voice returned
from TSR_START_DIGITAL.
* buff: If you are responding to UM_MORE_DATA
with UM_MORE_DATA, then this address should be
filled in with the address of your next buffer.
* size: If you are responding to UM_MORE_DATA
with UM_MORE_DATA, then this address should be
filled in with the size of your next buffer.
* rate: If you are responding to UM_MORE_DATA
with UM_MORE_DATA, then this address should be
filled in with the sample rate of your next
buffer. This option was added because DIGPAK
required double buffering with varying rates
for playback. The rate will not change until
the new buffer starts playback. If you need to
modify the rate immediately, call TSR_SET_RATE.
input:
es:di - far pointer to sound structure
output:
AX will have the voice that was allocated or -1 if
no voice was available. The voice field in the
structure will also contain the value.
TSR_PLAY_NEXT_BUFFER equ 1
If the callback routine is called and you pause the
Forte Technologies
UltraMID Programmer's Reference Guide Page 18
Chapter 6 - List of functions supported by UltraMID
voice, this routine can be called to send UltraMID the
next buffer and also continue playback.
input:
es:di - far pointer to sound structure
output:
none
TSR_SET_PAN equ 2
This function will be ignored for stereo data. For a
mono piece, it will let you set the position of the
sound from 0 left to 15 right. The total power of the
left and right line level outputs is maintained as you
pan from one side to the other.
input:
cx - voice
bx - pan
output:
none
TSR_SET_VOLUME equ 3
This function will let you change the output volume for
a specific voice. The range of volumes is from 0 to
4095. This call actually does a volume ramp from the
old volume to the new volume. It will actually take
about 1ms (maximum) to reach the desired volume. This
will prevent any clicks or pops in the audio. VERY
TECHNICAL NOTE: If you split up the 12 bit volume
number, the most significant four bits are the
exponent, and the least significant 8 bits are the
mantissa. EEEEMMMMMMMM. Ignoring pan, for a
peak-to-peak 16-bit sine wave, the line level output
voltage (RMS) is:
Lvrms = 1.7 * (256+M) / (512 * (2 ^ (15-E)))
which gives you a line level output range from 0 to 1.7
volts. This means for a volume of 0, you will get
-91.7dbv, and for a volume of 4095, you will get
+4.6dbv. This means the UltraSound has a range of
96dB!. 96dB is extremly loud and can hurt your ears.
More information on volume control: Since the
UltraSound card is always summing 24 voices, you should
keep the volumes below the maximum so that clipping
doesn't occur. The highest midi volume is 4016. The
digital volume can go up to 4095, but it might cause
noise in the output if there are other sounds playing.
You will get good results if you limit your audible
digital output to the range between 2048 and 4016. If
it turns out your MIDI music is too loud, the user can
lower the midi volume by running UltraMID with the -m
parameter. For example, UltraMID -m100.
Input:
Forte Technologies
UltraMID Programmer's Reference Guide Page 19
Chapter 6 - List of functions supported by UltraMID
cx - voice
bx - volume
output:
none
TSR_SET_RATE equ 4
not implemented yet
TSR_PAUSE_DIGITAL equ 5
This function will pause a voice immediately. It can
be restarted with TSR_RESTART_DIGITAL. No volume ramp
occurs with this function. If you want to pause a
playing voice and make sure there are no 'clicks' in
the audio, then set the volume to 0 before pausing.
When restarting, set the volume back to where it was.
input:
cx - voice
output:
none
TSR_RESTART_DIGITAL equ 6
This function will restart a voice that was paused with
TSR_PAUSE_DIGITAL. Take a look at TSR_PAUSE_DIGITAL
above for more information.
input:
cx - voice
output:
none
TSR_STOP_DIGITAL equ 7
This function will stop a playing voice. The callback
routine will get called immediately to alert your
application that the voice has been deallocated.
input:
cx - voice
output:
none
TSR_GET_DIGITAL_POSITION equ 8
This function will give you an approximate location of
the sample data that is being played for a particular
voice. The location will be a far pointer to somewhere
in your PC buffer.
input:
cx - voice
output:
ax:dx far pointer to your PC buffer. The pointer
may be normalized and not have the same segment
that you passed in.
TSR_VOICE_STATUS equ 9
not implemented yet
; MIDI functions
TSR_LOAD_MIDI_PATCHES equ 10
This function will automatically load the patches that
will be needed to play a midi track from a standard
midi file.
Forte Technologies
UltraMID Programmer's Reference Guide Page 20
Chapter 6 - List of functions supported by UltraMID
input:
es:di is far pointer to the midi track just after
the Mtrk header and length.
bx:dx is the length of the midi track
output:
none
TSR_LOAD_XMIDI_PATCHES equ 11
This function will automatically load the patches that
will be needed to play an xmidi track.
input:
es:di is far pointer to the event buffer including
the EVNT header
output:
none
TSR_LOAD_PATCH equ 12
This function will load a specific general midi patch.
input:
cx is the midi number of the patch to load.
(0-127 is a melodic patch)
(128-255 is a percussion patch)
output:
none
TSR_UNLOAD_PATCH equ 13
This function will remove a patch loaded with
TSR_LOAD_PATCH.
input:
cx is the midi number of the patch to unload.
(0-127 is a melodic patch)
(128-255 is a percussion patch)
output:
none
TSR_START_SEQUENCE equ 14
No parameters. This function alerts UltraMID that on
the next LOAD_XMIDI_PATCHES or LOAD_MIDI_PATCHES, that
it is ok to clear the UltraSound's memory before
loading new patches.
TSR_UNLOAD_ALL_PATCHES equ 15
No parameters. Clears all of the patches from the
UltraSound's memory.
TSR_MIDI_OUT equ 16
Pass one byte to the midi interpreter in UltraMID.
input:
cx - midi byte
output:
none
TSR_MIDI_OUT_STR equ 17
Play one or more bytes of midi data (immediately. No
timing information should be in this data)
input:
es:di far pointer to string of midi data
cx is the number of bytes.
Forte Technologies
UltraMID Programmer's Reference Guide Page 21
Chapter 6 - List of functions supported by UltraMID
output:
none
TSR_ALL_NOTES_OFF equ 18
start the release for any playing MIDI notes
; resource functions
TSR_ALLOCATE_MEMORY equ 19
Allocate memory for digital audio playback. If patches
are preloaded using the "-c" command, there will be at
least 8K of memory in all memory configurations.
input:
bx:dx amount of memory to allocate from
UltraSound's memory
output:
address in ax:dx, or 0:0 in ax:dx if memory not
available.
TSR_FREE_MEMORY equ 20
input:
bx:dx is the address of the memory on the
UltraSound card to free
output:
none
TSR_FREE_TSR equ 21
Your application shouldn't call this. This will cause
the TSR to stop all playing sounds, and free it's
memory back to DOS. inputs:
none
outputs:
none
TSR_SEM_ENTER equ 22
returns non-zero value if ok to call midi functions
from an interrupt
TSR_SEM_LEAVE equ 23
must call this after successful call to TSR_SEM_ENTER.
TSR_ADD_EXTERNAL_SEMAPHORE equ 24
This call is provided if you are going to use write
your own TSR around UltraMID, and it must be compatible
with previous versions of that TSR, and, that TSR has a
semaphore, AND, that TSR can report the address of it's
own semaphore to another caller, AND it has been
compiled in a small memory model. This call will let
you provide UltraMID with a far pointer to a memory
location which UltraMID will use to let your TSR know
that it is in a critical region of code and cannot be
re-entered from an interrupt.
input:
bx:dx is a far pointer to your applications
semaphore
output:
none
TSR_CLEAR_EXTERNAL_SEMAPHORE equ 25
Tells UltraMID to stop wasting time setting and
Forte Technologies
UltraMID Programmer's Reference Guide Page 22
Chapter 6 - List of functions supported by UltraMID
restoring an external semaphore set with
TSR_ADD_EXTERNAL_SEMAPHORE.
input:
none.
output:
none.
TSR_APP_START equ 26
This function tells UltraMID that an application will
be using the UltraMID vector.
TSR_APP_END equ 27
This function tells UltraMID that an application is
done using the UltraMID vector.
TSR_SET_MIDI_CALLBACK equ 28
is used to get data from the UART. This function sets
up a callback which will get called whenever midi data
is available or a communications error occurs. Your
function should have the following prototype:
int far midi_receive_handler(int status, int data)
The status variable will have the following bits set if
there is an error. The bit UM_ERR_FRAMING will be set
if there is a framing error, and the bit UM_ERR_OVERRUN
will be set if there is a data overrun. If neither of
the two error bits are set, then the variable data will
contain one midi data byte.
input:
es:di is a far pointer to your applications
callback function.
output:
AX will have a non-zero function if the UART could
not be activated or if the version of UltraMID in
use does not have the UART functions.
TSR_CLEAR_MIDI_CALLBACK equ 29
is used to clear the midi interrupt and disable the
MIDI callback function. See TSR_SET_MIDI_CALLBACK.
input:
none.
output:
none.
Forte Technologies
UltraMID Programmer's Reference Guide Page 23
Chapter 7 - How to locate the TSR in memory:
How to locate the TSR in memory:
************************************************************
UltraMID tries to use an interrupt vector not in use
between interrupt vectors 0x78 and 0x7F inclusive. This
vector can be overriden with the -v command line parameter.
The segment of the vector which UltraMID uses will have the
string "ULTRAMID" located at an offset of 0x103. In
pseudo-code:
for vector = 0x78 to 0x7f do
if far_pointer(seg(vector), 0x103) == "ULTRAMID" then
tsr_entry_point = far_pointer(seg(vector), off(vector))
end if
end for
In masm style assembly, here is a code segment that will
find the TSR.
; in your data segment
chk_hook_str db 'ULTRAMID'
ultramid_hook label dword
hookoff dw ?
hookseg dw ?
; in your code segment
push ds
mov al,078h
UltraMID is a TSR program that has approximately 20 functions to support
mov cx,6
next_vector:
mov ah,035h
int 21h
; es is segment of gf1 driver.
; di is offset into ULTRAMID.EXE's vector stamp.
mov di,0103H
mov si,offset chk_hook_str
push cx
mov cx,8
cld
repe cmpsb
jcxz vector_valid
; Setup for next vector check.
Forte Technologies
UltraMID Programmer's Reference Guide Page 24
Chapter 7 - How to locate the TSR in memory:
pop cx
inc al
loop next_vector
jmp vector_not_found
vector_valid:
pop cx
pop ds
mov hookoff, bx
mov hookseg, es
;OK YOU GOT IT - NOW RETURN
mov ax, 1
ret
vector_not_found:
pop ds
;Couldn't find it
xor ax, ax
ret
Forte Technologies
FILE: UMWAV.C
/***************************************************************************
* NAME: UMWAV.C
** COPYRIGHT:
** "Copyright (c) 1991,1992, by Forte Technologies
**
** "This software is furnished under a license and may be used,
** copied, or disclosed only in accordance with the terms of such
** license and with the inclusion of the above copyright notice.
** This software or any other copies thereof may not be provided or
** otherwise made available to any other person. No title to and
** ownership of the software is hereby transfered."
****************************************************************************
* CREATION DATE: 07/01/93
*--------------------------------------------------------------------------*
* VERSION DATE NAME DESCRIPTION
*> 1.0 07/01/93 umwav.c wav player example
***************************************************************************/
#include <dos.h>
#include "ultramid.h"
/* The first half of this file contains the interface functions to */
/* UltraMID, and the second half contains the file i/o, wav i/o, and */
/* user interface */
#pragma inline
#define MK_LONG (long)((void _seg *)(_DX) + (void near *)(_AX))
void (far *um_hook)(void) = 0L;
short volume = 4095;
short pan = 7;
short voice;
unsigned short frequency;
int still_playing;
int user_request_stop = 0;
#define NBUFFS 10
int play_index; /* pointer to buffer which is being played */
int dma_index; /* pointer to buffer which is being xferred */
int fill_index; /* pinter to buffer which should be filled next */
#define BS_EMPTY 0
#define BS_FILLED 1
#define BS_XFERRED 2
#define BS_PLAYING 3
unsigned char buff_status[NBUFFS];
unsigned long buff_len[NBUFFS];
#define BSIZE 4096U
#define GF1BSIZE 8192L
unsigned char buffs[NBUFFS][BSIZE];
unsigned char stbuff[GF1BSIZE/4];
Page #1
FILE: UMWAV.C
#pragma warn -par
int far um_callback(int reason, int voice, unsigned char far * far*buf, unsigned long far *size, unsigned short far *rate)
{
/* this function is called from an interrupt */
/* restore our DS register */
asm push ds
asm mov ax, cs
asm mov ds, ax
switch (reason) {
case UM_STOP_SOUND:
still_playing = 0;
break;
case UM_MORE_DATA:
if (buff_status[dma_index] == BS_FILLED) {
*buf = buffs[dma_index];
*size = buff_len[dma_index];
*rate = frequency;
buff_status[dma_index] = BS_XFERRED;
dma_index = (dma_index + 1) % NBUFFS;
asm pop ds
return(1); /* return 1 when there is more data */
/* return 0 when done playing */
}
break;
case UM_BUFFER_DONE:
buff_status[play_index] = BS_EMPTY;
play_index = (play_index + 1) % NBUFFS;
if (buff_status[play_index] == BS_XFERRED) {
buff_status[play_index] = BS_PLAYING;
}
break;
}
asm pop ds
return(0);
}
#pragma warn .par
int um_start_digital(struct um_sound_struct *umss)
{
_ES = FP_SEG(umss);
_DI = FP_OFF(umss);
_AX = TSR_START_DIGITAL;
(*um_hook)();
return(_AX);
}
void um_stop_digital(int voice)
{
Page #2
FILE: UMWAV.C
_CX = voice;
_AX = TSR_STOP_DIGITAL;
(*um_hook)();
}
void um_restart_digital(int voice)
{
_CX = voice;
_AX = TSR_RESTART_DIGITAL;
(*um_hook)();
}
void um_pause_digital(int voice)
{
_CX = voice;
_AX = TSR_PAUSE_DIGITAL;
(*um_hook)();
}
void um_set_pan(int voice, int pan)
{
_CX = voice;
_BX = pan;
_AX = TSR_SET_PAN;
(*um_hook)();
}
void um_set_volume(int voice, int volume)
{
_CX = voice;
_BX = volume;
_AX = TSR_SET_VOLUME;
(*um_hook)();
}
long um_allocate_memory(long mem)
{
asm mov dx, word ptr mem;
asm mov bx, word ptr mem+2;
_AX = TSR_ALLOCATE_MEMORY;
(*um_hook)();
mem = MK_LONG;
return(mem);
}
void um_free_memory(long mem)
{
asm mov bx, word ptr mem+2;
asm mov dx, word ptr mem;
Page #3
FILE: UMWAV.C
_AX = TSR_FREE_MEMORY;
(*um_hook)();
}
struct um_sound_struct umss;
/**********************************************************************/
/* file i/o functions, console functions, and other library funcs */
/**********************************************************************/
#define CARRY_FLAG 1
void interrupt (*getvect(int int_number))()
{
_AX = 0x3500;
_AL = (char)int_number;
geninterrupt(0x21);
return(MK_FP(_ES, _BX));
}
int my_strncmp(char far *str1, char far *str2, int len)
{
asm mov dx, ds
asm cld
asm les di, str2
asm mov si, di
asm mov ax, len
asm mov cx, ax
asm jcxz done
asm mov bx, ax
asm xor al, al
asm repne scasb
asm sub bx, cx
asm mov cx, bx
asm mov di, si
asm lds si, str1
asm repe cmpsb
asm mov al, [si-1]
asm mov bl, es:[di-1]
asm xor ah, ah
asm mov bh, ah
asm sub ax, bx
done:
asm mov ds, dx
return(_AX);
}
Page #4
FILE: UMWAV.C
void myputs(char *s)
{
while (*s) {
_BH = 0;
_AL = *s;
_AH = 0x0e;
geninterrupt(0x10);
s++;
}
}
mygetch(void)
{
_AX = 0x0700;
geninterrupt(0x21);
_AH = 0x0;
return(_AL);
}
my_open(char *name)
{
int save_ax, hold_ds;
hold_ds = FP_SEG( name );
_DX = FP_OFF( name );
_AH = 0x3d; /* open file */
_AL = 0x0; /* read only */
asm push ds;
_DS = hold_ds;
geninterrupt(0x21);
asm pop ds;
save_ax = _AX;
if( _FLAGS & CARRY_FLAG ) {
return( -1 );
}
return(save_ax);
}
void my_close(int handle)
{
_BX = handle;
_AH = 0x3e;
geninterrupt(0x21);
}
my_read(int handle, unsigned char *buff, unsigned long lsize)
Page #5
FILE: UMWAV.C
{
int hold_ds;
unsigned int size;
unsigned short seg, offset;
unsigned long addr;
while (lsize > 0) {
size = (lsize > 32768U) ? 32768U : lsize;
lsize -= size;
hold_ds = FP_SEG(buff);
_DX = FP_OFF(buff);
_BX = handle;
_CX = size;
_AH = 0x3f; /* read file */
asm push ds;
_DS = hold_ds;
geninterrupt(0x21);
asm pop ds;
if (_FLAGS & CARRY_FLAG) {
return(0);
}
buff += size;
}
return(1);
}
/**********************************************************************/
/* Application Functions */
/**********************************************************************/
struct riff_header {
char RIFF[4];
unsigned long file_size;
char WAVE[4];
char fmt[4];
unsigned long format_size;
} riff_header;
struct format_header {
unsigned int wFormatTag;
unsigned int nChannels;
unsigned long nSamplesPerSec;
unsigned long nAvgBytesPerSec;
unsigned int nBlockAlign;
unsigned int nBitsPerSample;
} format_header;
struct data_header {
unsigned char DATA[4];
Page #6
FILE: UMWAV.C
unsigned long size;
} data_header;
#define MIN(a,b) ((a) < (b) ? (a) : (b))
int far *head_keys = (int far *)0x0040001aL;
int far *tail_keys = (int far *)0x0040001cL;
#define fast_kbhit() (*head_keys != *tail_keys)
void check_buffers(int fp, unsigned long *length)
{
unsigned long size;
static int paused=0;
int c;
if (fast_kbhit()) {
if ((c=mygetch()) == 27) {
user_request_stop = 1;
um_stop_digital(voice);
return;
} else if (c == 'P' || c == 'p') {
if (paused) {
um_restart_digital(voice);
} else {
um_pause_digital(voice);
}
paused ^= 1;
} else if (c == '>') {
if (pan > 0) {
um_set_pan(voice, --pan);
}
} else if (c == '<') {
if (pan < 15) {
um_set_pan(voice, ++pan);
}
} else if (c == ',') {
volume -= 50;
if (volume < 0) volume = 0;
um_set_volume(voice, volume);
} else if (c == '.') {
volume += 50;
if (volume > 4095) volume = 4095;
um_set_volume(voice, volume);
}
}
while (buff_status[fill_index] == BS_EMPTY) {
/* check to see if there is any more data to read */
if (*length == 0) break; /* if no more data in file - return */
Page #7
FILE: UMWAV.C
/* fill next buffer */
size = MIN(BSIZE, *length);
my_read(fp, buffs[fill_index], size);
*length -= size;
buff_len[fill_index] = size;
buff_status[fill_index] = BS_FILLED;
fill_index = (fill_index + 1) % NBUFFS;
}
}
void play(char *filename, unsigned long gf1mem)
{
int fp;
int i;
unsigned long length;
unsigned short type=0;
fp = my_open(filename);
if (fp == -1) {
myputs("Can't open file ");
myputs(filename);
myputs(" for playback");
return;
}
my_read(fp, (unsigned char *)&riff_header, sizeof(riff_header));
if (my_strncmp(riff_header.RIFF, "RIFF", 4) == 0 &&
my_strncmp(riff_header.WAVE, "WAVE", 4) == 0 &&
my_strncmp(riff_header.fmt, "fmt ", 4) == 0) {
/* read header */
if (my_read(fp, (unsigned char *)&format_header, riff_header.format_size) != 1) {
myputs("File ");
myputs(filename);
myputs(" isn't a .WAV format file");
my_close(fp);
return;
}
if (format_header.wFormatTag != 1) {
myputs("This player can't play ");
myputs(filename);
my_close(fp);
return;
}
if (format_header.nChannels != 1) {
type |= UM_STEREO;
}
if (format_header.nBitsPerSample == 8) {
type |= UM_8BIT;
type |= UM_INVERT_MSB;
} else if (format_header.nBitsPerSample != 16) {
Page #8
FILE: UMWAV.C
myputs("Can't play ");
myputs(filename);
myputs(". Must be 8 or 16 bits");
my_close(fp);
return;
}
frequency = format_header.nSamplesPerSec;
my_read(fp, (unsigned char *)&data_header, sizeof(data_header));
if (my_strncmp(data_header.DATA, "data", 4)) {
myputs("This player can't play ");
myputs(filename);
myputs(". Can't find data");
my_close(fp);
return;
}
length = data_header.size;
user_request_stop = 0;
umss.stereo_mem = stbuff;
umss.gf1mem = gf1mem;
umss.pan = pan;
umss.volume = volume;
umss.sample_rate = frequency;
umss.priority = 0;
umss.data_type = type;
umss.callback = um_callback;
for (i=0; i < NBUFFS; i++) buff_status[i] = BS_EMPTY;
fill_index = 0;
play_index = 0;
dma_index = 0;
while (user_request_stop == 0 && length != 0) {
check_buffers(fp,&length);
buff_status[play_index] = BS_PLAYING;
umss.sound_data = buffs[play_index];
umss.sound_len = buff_len[play_index];
dma_index = (dma_index + 1) % NBUFFS;
still_playing = 1;
voice = um_start_digital(&umss);
if (voice == -1) break;
while (still_playing) {
check_buffers(fp,&length);
}
}
}
my_close(fp);
}
int main(void)
{
unsigned long mem;
Page #9
FILE: UMWAV.C
int ret, i;
int vector;
char far *stamp;
char filename[80], *cp;
char far *command;
unsigned char far *command_len;
command_len = MK_FP(_psp, 0x80);
command = MK_FP(_psp,0x82);
if (*command_len <= 1) return(0);
command[*command_len-1] = '\0';
cp = filename;
while ((*cp++ = *command++) != 0) ;
for (vector=0x78; vector <= 0x7f; vector++) {
um_hook = (void (far *)())getvect(vector);
stamp = (char far *)MK_FP(FP_SEG(um_hook), 0x103);
if (my_strncmp(stamp, "ULTRAMID", 8) == 0) break;
}
if (vector > 0x7f) {
myputs("Couldn't find UltraMID\r\n");
return(3);
}
mem = um_allocate_memory(0x2000L);
if (mem == 0L) {
myputs("UltraSound card - out of memory");
} else {
play(filename, mem);
um_free_memory(mem);
}
return(0);
}
Page #10
FILE: AUMWAV.ASM
;***************************************************************************
; NAME: aumwav.asm
; COPYRIGHT:
; "Copyright (c) 1993 by Forte Technologies
;
; "This software is furnished under a license and may be used,
; copied, or disclosed only in accordance with the terms of such
; license and with the inclusion of the above copyright notice.
; This software or any other copies thereof may not be provided or
; otherwise made available to any other person. No title to and
; ownership of the software is hereby transfered."
;***************************************************************************
; CREATION DATE: 07/01/92
;--------------------------------------------------------------------------*
; VERSION DATE NAME DESCRIPTION
;> 1.0 07/01/93 aumwav.asm wav player example
;**************************************************************************/
; LOCALS @@
_TEXT segment byte public 'CODE'
_TEXT ends
assume cs:_TEXT, ds:_TEXT
include ultramid.inc
riff_header struc
rh_RIFF dd ?
rh_file_size dd ?
rh_WAVE dd ?
rh_fmt dd ?
rh_format_size dd ?
riff_header ends
format_header struc
fh_wFormatTag dw ?
fh_nChannels dw ?
fh_nSamplesPerSec dd ?
fh_nAvgBytesPerSec dd ?
fh_nBlockAlign dw ?
fh_nBitsPerSample dw ?
format_header ends
data_header struc
dh_DATA dd ?
dh_size dd ?
data_header ends
_TEXT segment byte public 'CODE'
org 100h
start: call _main
Page #1
FILE: AUMWAV.ASM
mov ah,4ch
int 21h
__psp dw ?
_um_hook label dword
_hookoff dw 0
_hookseg dw 0
_volume dw 4095
_pan dw 7
_paused db 0
_user_request_stop db 0
_head_keys label dword
dw 1ah
dw 40h
_tail_keys label dword
dw 1ch
dw 40h
_umss um_sound_struct <>
chk_hook_str db 'ULTRAMID',0
riffheader riff_header <>
formatheader format_header <>
dataheader data_header <>
BS_EMPTY equ 0
BS_FILLED equ 1
BS_XFERRED equ 2
BS_PLAYING equ 3
NBUFFS equ 4
BSIZE equ 8192
BSIZE_BITS equ 13
_um_callback proc C far,reason,voice,buff:FAR PTR,bufflen:FAR PTR, bufrate:FAR PTR
uses ds, si, di
mov ax, cs
mov ds, ax
mov ax,reason
cmp ax,UM_STOP_SOUND
je short @@stop
cmp ax,UM_MORE_DATA
je short @@more_data
cmp ax,UM_VOICE_DONE
je short @@voice_done
mov ax, 0
jmp @@ret
@@stop:
Page #2
FILE: AUMWAV.ASM
mov _still_playing,0
jmp @@ret
@@more_data:
mov bx,_dma_index
mov ax, 0
cmp _buff_status[bx],BS_FILLED
jne short @@ret
mov cl,BSIZE_BITS ; find addr of buffer (_dma_index * BSIZE + offset(buffs))
shl bx,cl
add bx,offset _buffs
; fill in buff with next buffer to play
les si,buff
mov word ptr es:[si+2],ds
mov word ptr es:[si],bx
; fill in bufflen with size of next buffer to play
mov bx,_dma_index
mov cl,2
shl bx,cl
mov ax,_buff_len[bx+2]
mov dx,_buff_len[bx]
les bx,bufflen
mov word ptr es:[bx+2],ax
mov word ptr es:[bx],dx
; fill in rate with frequency of next buffer
les bx,bufrate
mov ax,_frequency
mov word ptr es:[bx],ax
; change status of _buff_status to BS_XFERRED
mov bx,_dma_index
mov byte ptr _buff_status[bx],BS_XFERRED
; bump _dma_index
mov ax,bx
inc ax
mov bx,NBUFFS
cwd
idiv bx
mov word ptr _dma_index,dx
mov ax,1
jmp @@ret
@@voice_done:
mov bx,_play_index
mov _buff_status[bx],BS_EMPTY
; bump _play_index
mov ax,_play_index
inc ax
mov dx, 0
mov cx,NBUFFS
idiv cx
mov _play_index,dx
Page #3
FILE: AUMWAV.ASM
; if buffer was xferred already change status to BS_PLAY
mov bx, dx
cmp _buff_status[bx],BS_XFERRED
jne short @@ret
mov _buff_status[bx],BS_PLAYING
@@ret:
ret
_um_callback endp
_my_strncmp proc C near, str1:NEAR PTR, str2:NEAR PTR, len:word
uses ds, si, di
cld
mov ax, cs
mov es, ax
mov di, str2
mov si, di
mov ax, len
mov cx, ax
jcxz short @@done
mov bx, ax
xor al, al
repne scasb
sub bx, cx
mov cx, bx
mov di, si
mov si, str1
repe cmpsb
mov al, [si-1]
mov bl, es:[di-1]
xor ah, ah
mov bh, ah
sub ax, bx
@@done:
ret
_my_strncmp endp
_myputs proc C near string:NEAR PTR
uses si
mov si,string
jmp short @@end_while
@@while:
mov bh,0
mov al, [si]
mov ah,14
int 10h
inc si
@@end_while:
cmp byte ptr [si],0
Page #4
FILE: AUMWAV.ASM
jne short @@while
ret
_myputs endp
_mygetch proc C near
mov ax,700h
int 21h
xor ah,ah
ret
_mygetch endp
_my_open proc C near fname:NEAR PTR
mov dx,fname
mov ah,03dh
mov al,0
int 21h
jnc short @@ret
mov ax,-1
@@ret:
ret
_my_open endp
_my_close proc C near handle
mov bx, handle
mov ah,3eh
int 21h
ret
_my_close endp
_my_read proc C near handle, buff, bsize
mov dx,buff
mov bx,handle
mov cx,bsize
mov ah,3fh
int 21h
jc short @@ret
mov ax, 1
jmp short @@ret1
@@ret:
xor ax, ax
@@ret1: ret
_my_read endp
_check_buffers proc C near fp, blength:NEAR ptr
local bsize, c
uses si, di
; check the keyboard
les bx,_head_keys
Page #5
FILE: AUMWAV.ASM
mov ax, word ptr es:[bx]
les bx,_tail_keys
cmp ax,word ptr es:[bx]
jne @@check_key
jmp @@check_buffers
@@check_key:
call _mygetch C
mov si,ax
cmp ax,27 ; check esc key
jne short @@check_key_1
mov _user_request_stop,1
mov ax, TSR_STOP_DIGITAL
mov cx, _voice
call dword ptr _um_hook
jmp @@ret
@@check_key_1:
cmp si,'P'
je short @@p_pressed
cmp si,'p'
jne short @@check_key_2
@@p_pressed:
cmp _paused,0
je @@pause
mov ax, TSR_RESTART_DIGITAL
mov cx, _voice
call dword ptr _um_hook
jmp short @@flip_paused
@@pause:
mov ax, TSR_PAUSE_DIGITAL
mov cx, _voice
call dword ptr _um_hook
@@flip_paused:
xor _paused,1
jmp @@check_buffers
@@check_key_2:
cmp si,'>'
jne short @@check_key_3
cmp _pan,0
jle short @@check_key_3
dec _pan
@@set_pan:
mov bx,_pan
mov cx,_voice
mov ax, TSR_SET_PAN
call dword ptr _um_hook
jmp short @@check_buffers
@@check_key_3:
cmp si,'<'
jne short @@check_key_4
Page #6
FILE: AUMWAV.ASM
cmp _pan,15
jge short @@check_key_4
inc _pan
jmp short @@set_pan
@@check_key_4:
cmp si,','
jne short @@check_key_5
sub _volume,50
jnc @@set_volume
mov _volume,0
@@set_volume:
mov bx, _volume
mov cx, _voice
mov ax, TSR_SET_VOLUME
call dword ptr _um_hook
jmp short @@check_buffers
@@check_key_5:
cmp si,'.'
jne short @@check_buffers
add _volume,50
cmp _volume,4095
jle short @@set_volume
mov _volume,4095
jmp short @@set_volume
@@check_buffers:
mov di, word ptr [blength]
jmp @@end_while
@@while:
; check if next buffer is empty
mov ax,word ptr [di]
or ax,word ptr [di+2]
jne short @@fill_buffer
jmp @@ret
@@fill_buffer:
; bsize = MIN(BSIZE, blength)
cmp word ptr [di+2],0
ja short @@use_BSIZE
cmp word ptr [di],BSIZE
jbe short @@use_size
@@use_BSIZE:
mov si,BSIZE
jmp short @@do_read
@@use_size:
mov si,word ptr [di]
@@do_read:
mov ax,_fill_index
mov cl,BSIZE_BITS
shl ax,cl
add ax,offset _buffs
Page #7
FILE: AUMWAV.ASM
call _my_read C, fp, ax, si
sub word ptr [di],si
sbb word ptr [di+2],0
mov bx,_fill_index
mov cl,2
shl bx,cl
mov word ptr _buff_len[bx+2],0
mov word ptr _buff_len[bx],si
mov bx,_fill_index
mov _buff_status[bx],BS_FILLED
mov ax,bx
inc ax
mov bx,NBUFFS
cwd
idiv bx
mov _fill_index,dx
@@end_while:
mov bx,_fill_index
cmp _buff_status[bx],BS_EMPTY
jne @@ret
jmp @@while
@@ret: ret
_check_buffers endp
_play proc C near filename:NEAR PTR, gf1mem:dword
local fp, i, blength:dword, btype
uses si, di
mov btype, 0
call _my_open C,[filename]
mov fp, ax
cmp ax, -1
jne short @@get_riff_header
mov ax,offset msg_cant_open
call _myputs C,ax
call _myputs C,[filename]
mov ax,offset msg_cant_open1
call _myputs C,ax
jmp @@ret
@@get_riff_header:
mov ax, offset riffheader
mov cx, size riffheader
call _my_read C, fp, ax, cx
mov bx, offset riffheader.rh_RIFF
mov cx, offset msg_RIFF
call _my_strncmp C, bx, cx, 4
or ax,ax
je @@2
jmp @@not_wav
Page #8
FILE: AUMWAV.ASM
@@2:
lea bx, riffheader.rh_WAVE
lea cx, msg_WAVE
mov ax,4
call _my_strncmp C, bx, cx, 4
or ax,ax
je @@3
jmp @@not_wav
@@3:
lea bx, riffheader.rh_fmt
lea cx, msg_fmt
mov ax,4
call _my_strncmp C, bx, cx, 4
or ax,ax
je @@4
jmp @@not_wav
@@4:
mov bx, word ptr riffheader.rh_format_size
mov ax, offset formatheader
call _my_read C, fp, ax, bx
cmp ax,1
je short @@check_tag
@@not_wav:
mov ax, offset msg_File
call _myputs C, ax
call _myputs C, filename
mov ax, offset msg_isnt_wave
call _myputs C, ax
@@close:
call _my_close C, fp
jmp @@ret
@@check_tag:
cmp word ptr formatheader.fh_wFormatTag,1
je short @@check_nchannels
mov ax, offset msg_cant_play
call _myputs C, ax
call _myputs C, filename
jmp short @@close
@@check_nchannels:
cmp word ptr formatheader.fh_nChannels,1
je @@check_nbits
or btype, UM_STEREO
@@check_nbits:
cmp word ptr formatheader.fh_nBitsPerSample,8
jne short @@check_16bits
or btype, (UM_8BIT OR UM_INVERT_MSB)
jmp short @@get_frequency
@@check_16bits:
cmp word ptr formatheader.fh_nBitsPerSample,16
Page #9
FILE: AUMWAV.ASM
je short @@get_frequency
mov ax, offset msg_cant_play
call _myputs C, ax
call _myputs C, filename
mov ax, offset msg_bad_type
call _myputs C, ax
jmp short @@close
@@get_frequency:
mov ax,word ptr formatheader.fh_nSamplesPerSec
mov word ptr _frequency,ax
mov ax, offset dataheader
mov bx, size dataheader
call _my_read C, fp, ax, bx
lea bx, dataheader.dh_DATA
mov ax, offset msg_data
call _my_strncmp C, bx, ax, 4
or ax,ax
je short @@get_length
mov ax, offset msg_cant_play
call _myputs C, ax
call _myputs C, filename
mov ax, offset msg_no_data
call _myputs C, ax
jmp @@close
@@get_length:
mov ax,word ptr dataheader.dh_size+2
mov dx,word ptr dataheader.dh_size
mov word ptr [blength+2],ax
mov word ptr [blength],dx
mov _user_request_stop,0
mov _umss.um_stereo_mem_seg,ds
mov _umss.um_stereo_mem_off,offset _stbuff
mov ax,word ptr [gf1mem+2]
mov dx,word ptr [gf1mem]
mov word ptr _umss.um_gf1mem+2,ax
mov word ptr _umss.um_gf1mem,dx
mov al,byte ptr _pan
mov byte ptr _umss.um_pan,al
mov ax,word ptr _volume
mov word ptr _umss.um_volume,ax
mov ax,word ptr _frequency
mov word ptr _umss.um_sample_rate,ax
mov word ptr _umss.um_priority,0
mov al,byte ptr btype
mov byte ptr _umss.um_data_type,al
mov word ptr _umss.um_callback_addr_seg,cs
mov word ptr _umss.um_callback_addr_off,offset _um_callback
mov bx,0
@@empty_buffs:
Page #10
FILE: AUMWAV.ASM
mov _buff_status[bx],BS_EMPTY
inc bx
cmp bx, NBUFFS
jne short @@empty_buffs
mov word ptr _fill_index,0
mov word ptr _play_index,0
mov word ptr _dma_index,0
jmp @@end_while
@@while:
lea ax,blength
call _check_buffers C, fp, ax
mov bx,_play_index
mov _buff_status[bx],BS_PLAYING
mov ax,bx
mov cl,BSIZE_BITS
shl ax,cl
add ax,offset _buffs
mov word ptr _umss.um_sound_data_seg,ds
mov word ptr _umss.um_sound_data_off,ax
mov cl,2
shl bx,cl
mov ax,word ptr _buff_len[bx+2]
mov dx,word ptr _buff_len[bx]
mov word ptr _umss.um_sound_len+2,ax
mov word ptr _umss.um_sound_len,dx
mov ax,_dma_index
inc ax
mov bx,NBUFFS
cwd
idiv bx
mov _dma_index,dx
mov word ptr _still_playing,1
mov ax, cs
mov es, ax
mov di, offset _umss
mov ax, TSR_START_DIGITAL
call _um_hook
mov word ptr _voice,ax
cmp ax,-1
je short @@done
jmp @@check_playing
@@keep_on_checking:
lea ax, blength
call _check_buffers C, fp, ax
@@check_playing:
cmp word ptr _still_playing,0
jne short @@keep_on_checking
@@end_while:
cmp _user_request_stop,0
Page #11
FILE: AUMWAV.ASM
jne short @@done
mov ax,word ptr [blength]
or ax,word ptr [blength+2]
je @@done
jmp @@while
@@done:
jmp @@close
@@ret: ret
_play endp
_main proc C near
local gf1mem:dword
mov ax, cs
mov ds, ax
mov ax, es
mov __psp, ax
mov bh, 0
mov di, 80h
mov bl, byte ptr es:[di]
cmp bl, 1
jg @@find_filename
jmp @@ret
@@find_filename:
dec bx
mov si, 82h
mov byte ptr es:[bx+si], 0
; copy file name into this segment
push ds
push ds
mov ax, es
mov ds, ax
pop es
mov di, offset _filename
mov cx, 80
cld
rep movsb
pop ds
; find ultramid's entry point
mov al,078h
mov cx,6
@@next_vector:
mov ah,035h
int 21h
; es is segment of gf1 driver.
; di is Offset into UltraMID's vector stamp.
mov di,0103H
Page #12
FILE: AUMWAV.ASM
mov si,offset chk_hook_str
push cx
mov cx,8
cld
repe cmpsb
jcxz @@vector_valid
; Setup for next vector check.
pop cx
inc al
loop @@next_vector
jmp @@vector_not_found
@@vector_valid:
pop cx
mov ah,035h
int 21h
mov [_hookoff], bx
mov [_hookseg], es
mov ax, TSR_APP_START
call dword ptr _um_hook
xor bx,bx
mov dx,8192
mov ax, TSR_ALLOCATE_MEMORY
call dword ptr _um_hook
mov word ptr [gf1mem+2],dx
mov word ptr [gf1mem],ax
or ax,dx
je short @@no_memory
mov ax, offset _filename
call _play C, ax, [gf1mem]
mov dx, word ptr [gf1mem]
mov bx, word ptr [gf1mem+2]
mov ax, TSR_FREE_MEMORY
call dword ptr _um_hook
mov ax, TSR_APP_END
call dword ptr _um_hook
jmp short @@ret
@@no_memory:
mov ax, offset msg_no_mem
call _myputs C, ax
jmp short @@ret
@@vector_not_found:
mov ax, offset msg_no_um
call _myputs C, ax
jmp short @@ret
@@ret:
Page #13
FILE: AUMWAV.ASM
ret
_main endp
msg_cant_open db "Can't open file ", 0
msg_cant_open1 db ' for playback',0
msg_RIFF db 'RIFF',0
msg_WAVE db 'WAVE',0
msg_fmt db 'fmt ',0
msg_File db 'File ',0
msg_isnt_wave db " isn't a .WAV format file",0
msg_cant_play db "This player can't play ",0
msg_bad_type db '. Must be 8 or 16 bits',0
msg_data db 'data',0
msg_no_data db ". Can't find data",0
msg_no_um db "Couldn't find UltraMID",13,10,0
msg_no_mem db 'UltraSound card - out of memory',13,10,0
_filename db 80 dup (?)
_stbuff db 2048 dup (?)
_buffs db BSIZE*NBUFFS dup (?)
_buff_len label word
db 40 dup (?)
_buff_status label byte
db NBUFFS dup (?)
_fill_index dw ?
_dma_index dw ?
_play_index dw ?
_still_playing dw ?
_frequency dw ?
_voice dw ?
_TEXT ends
end start
Page #14
FILE: TOTSR.C
/*
** Copyright (C) 1993 Forte. All rights reserved
** Written late one night 6/27/93.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <dos.h>
#include "midi.h"
#include "totsr.h"
#include "ultramid.h"
extern int control_break;
/* interrupt clock stuff */
static void interrupt (*orig_clock)(void) = 0L;
static void interrupt clock(void);
static unsigned long clock_interval = 8334L; /* microseconds */
#define CLOCK_DIVISOR 9944U
#define ORIG_CLOCK_DIVISOR 0U
/* the timer interrupt is on vector 0x08 */
#define TIMER 0x08
void (far *um_hook)(void) = 0L;
int um_data_out( int byte )
{
if (um_hook) {
_AX = TSR_MIDI_OUT;
_CX = byte;
(*um_hook)();
}
return(0);
}
void um_pitch_bend( int channel, int msb, int lsb )
{
um_data_out( (pitch_wheel+channel) );
um_data_out( msb );
um_data_out( lsb );
}
void um_program( int channel, int prog )
{
um_data_out( (program_chng+channel) );
um_data_out( prog );
Page #1
FILE: TOTSR.C
}
void um_chanpressure( int channel, int pitch, int pressure )
{
um_data_out( (channel_aftertouch+channel) );
um_data_out( pitch );
um_data_out( pressure );
}
void um_pressure( int channel, int pitch, int pressure )
{
um_data_out( (poly_aftertouch+channel) );
um_data_out( pitch );
um_data_out( pressure );
}
void um_parameter( int channel, int control, int value )
{
um_data_out( (control_change+channel) );
um_data_out( control );
um_data_out( value );
}
int um_note_on( int channel, int note, int velocity )
{
int error;
error = um_data_out( (note_on+channel) );
error += um_data_out( note );
error += um_data_out( velocity );
return(error);
}
void um_note_off( int channel, int note, int velocity )
{
um_data_out( (note_off+channel) );
um_data_out( note );
um_data_out( velocity );
}
int reset_um( )
{
int i;
if (um_hook) {
_AX = TSR_ALL_NOTES_OFF;
(*um_hook)();
for (i=0; i < 16; i++) {
um_parameter(i, 0x78, 0); /* all sounds off */
Page #2
FILE: TOTSR.C
um_parameter(i, 0x79, 0); /* reset all controllers */
um_parameter(i, 100, 0); /* RPN MSB 0 */
um_parameter(i, 101, 0); /* RPN LSB 0 */
um_parameter(i, 6, 2); /* Pitch Bend Sensitivity MSB */
um_parameter(i, 38, 0); /* Pitch Bend Sensitivity LSB */
}
}
return(1);
}
static unsigned long us_timer = 0L;
static unsigned long timer = 0L;
static unsigned long tempo = 4800L;
static void interrupt clock()
{
short t;
us_timer += clock_interval;
if (us_timer > tempo) {
t = us_timer / tempo;
us_timer -= (t * tempo);
timer += t;
}
if (orig_clock) (*orig_clock)();
}
void um_set_tempo(us, divisor)
unsigned long us;
short divisor;
{
/*
** the tempo and divisor values are described in the Standard Midi
** File specification 1.0
*/
int smpte_format, smpte_resolution;
if (divisor > 0) {
tempo = us / divisor;
} else {
smpte_format = (-divisor >> 8) & 0x7f;
smpte_resolution = divisor & 0x7f;
tempo = 1000000 / (smpte_format * smpte_resolution);
}
if (tempo <= 0) tempo = 1;
}
void um_reset_tick_counter()
{
Page #3
FILE: TOTSR.C
timer = 0L;
us_timer = 0L;
}
int um_wait_for ( unsigned long some_time )
{
if (kbhit() || control_break) return(1);
while (some_time > timer) if (kbhit() || control_break) return(1);
return(0);
}
#pragma warn -par
int um_init_hardware(struct MIDILIB *ml)
{
int vector, i;
char far *stamp;
struct track *track;
unsigned long length;
for (vector=0x78; vector <= 0x7f; vector++) {
um_hook = (void (far *)())getvect(vector);
stamp = (char far *)MK_FP( FP_SEG(um_hook), 0x103 );
if (strncmp(stamp, "ULTRAMID", 8) == 0) break;
}
if (vector <= 0x7f) {
midi_message("UltraMID is now loading instrument patches");
if (um_hook) {
_AX = TSR_APP_START;
(*um_hook)();
}
reset_um();
if (um_hook) {
for (track=ml->Mp_tracks; track; track = track->next_track) {
_ES = FP_SEG(track->data);
asm push es
_DI = FP_OFF(track->data);
asm push di
length = track->length;
asm mov bx, word ptr length+2
asm mov dx, word ptr length
asm pop di
asm pop es
_AX = TSR_LOAD_MIDI_PATCHES;
(*um_hook)();
}
_AX = TSR_START_SEQUENCE;
(*um_hook)();
}
} else {
Page #4
FILE: TOTSR.C
um_hook = 0L;
midi_error("Couldn't find UltraMID");
return(1);
}
return(0);
}
#pragma warn .par
void um_cleanup( void )
{
reset_um();
if (um_hook) {
_AX = TSR_APP_END;
(*um_hook)();
}
}
void um_init_timers(void)
{
orig_clock = getvect(TIMER);
setvect(TIMER, clock);
outportb(0x43, 0x36);
outportb(0x40, CLOCK_DIVISOR & 0xff);
outportb(0x40, (CLOCK_DIVISOR & 0xff00) >> 8);
}
void um_cleanup_timers(void)
{
outportb(0x43, 0x36);
outportb(0x40, ORIG_CLOCK_DIVISOR & 0xff);
outportb(0x40, (ORIG_CLOCK_DIVISOR & 0xff00) >> 8);
if (orig_clock) setvect(TIMER, orig_clock);
}
Page #5
FILE: UMUART.C
/***************************************************************************
* NAME: UMIDI.C
** COPYRIGHT:
** "Copyright (c) 1991,1992, by Forte Technologies
**
** "This software is furnished under a license and may be used,
** copied, or disclosed only in accordance with the terms of such
** license and with the inclusion of the above copyright notice.
** This software or any other copies thereof may not be provided or
** otherwise made available to any other person. No title to and
** ownership of the software is hereby transfered."
****************************************************************************
* CREATION DATE: 09/11/93
*--------------------------------------------------------------------------*
* VERSION DATE NAME DESCRIPTION
*> 1.0 09/11/93 umidi.c example of how to use midi uart
***************************************************************************/
#include <conio.h>
#include <dos.h>
#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <string.h>
#include <malloc.h>
#include <stdlib.h>
#include "ultramid.h"
/* MIDI status commands most significant bit is 1 */
#define note_off 0x80
#define note_on 0x90
#define poly_aftertouch 0xa0
#define control_change 0xb0
#define program_chng 0xc0
#define channel_aftertouch 0xd0
#define pitch_wheel 0xe0
#define system_exclusive 0xf0
#pragma inline
extern unsigned __stklen = 4096;
int volume = 3900;
volatile int user_is_tired = 0;
/****************************************************************************/
/* TRAP CONTROL-C AND CONTROL-BREAK *****************************************/
/****************************************************************************/
int cbreak()
Page #1
FILE: UMUART.C
{
user_is_tired = 1;
return(1);
}
/****************************************************************************/
/* ULTRAMID INTERFACE FUNCTIONS *********************************************/
/****************************************************************************/
int (far *um_hook)(void) = 0L;
/* send a midi byte to to UltraMID */
int um_data_out( int byte )
{
if (um_hook) {
_AX = TSR_MIDI_OUT;
_CX = byte;
(*um_hook)();
}
return(0);
}
/* Tell UltraMID that this application is going to be using it */
void um_app_start(void)
{
if (um_hook) {
_AX = TSR_APP_START;
(*um_hook)();
}
}
/* Tell UltraMID that this application is done using it */
void um_app_end(void)
{
if (um_hook) {
_AX = TSR_APP_END;
(*um_hook)();
}
}
/* attempt to load a patch. 0-127 are melodic instruments */
/* 128-255 are percussion */
int um_load_patch( int prog )
{
int ax;
printf("loading patch %d\n", prog);
_CX = prog;
_AX = TSR_LOAD_PATCH;
ax = (*um_hook)();
Page #2
FILE: UMUART.C
return(ax);
}
void um_unload_patch( int prog )
{
_CX = prog;
_AX = TSR_UNLOAD_PATCH;
(*um_hook)();
}
/* Turn all currently playing notes off. You should call this function if */
/* there is a MIDI framing error or a data overrun error */
void um_all_notes_off( void )
{
_AX = TSR_ALL_NOTES_OFF;
(*um_hook)();
}
/* send a controller message to UltraMID */
void um_parameter(int chan, int controller, int value)
{
um_data_out(control_change|chan);
um_data_out(controller);
um_data_out(value);
}
/* Reset the UltraMID interpreter to its default state */
void reset_um(void)
{
int i;
if (um_hook) {
_AX = TSR_ALL_NOTES_OFF;
(*um_hook)();
for (i=0; i < 16; i++) {
um_parameter(i, 0x78, 0); /* all sounds off */
um_parameter(i, 0x79, 0); /* reset all controllers */
um_parameter(i, 100, 0); /* RPN MSB 0 */
um_parameter(i, 101, 0); /* RPN LSB 0 */
um_parameter(i, 6, 2); /* Pitch Bend Sensitivity MSB */
um_parameter(i, 38, 0); /* Pitch Bend Sensitivity LSB */
}
}
}
/* Find the UltraMID TSR and set up a dynamic link to it */
int um_link(void)
{
int vector;
Page #3
FILE: UMUART.C
char far *stamp;
for (vector=0x78; vector <= 0x7f; vector++) {
um_hook = (int (far *)())getvect(vector);
stamp = (char far *)MK_FP( FP_SEG(um_hook), 0x103 );
if (strncmp(stamp, "ULTRAMID", 8) == 0) break;
}
if (vector <= 0x7f) {
um_app_start();
reset_um();
} else {
um_hook = 0L;
printf("Couldn't find UltraMID");
return(1);
}
return(0);
}
/* Set up a callback to receive MIDI data from the UART */
int um_set_midi_callback( int (far *callback)() )
{
_ES = FP_SEG(callback);
_DI = FP_OFF(callback);
_AX = TSR_SET_MIDI_CALLBACK;
return((*um_hook)());
}
/* Reset the midi UART callback */
void um_clear_midi_callback( void )
{
um_set_midi_callback((int (far *)())0);
}
/* unlink from UltraMID */
void um_unlink(void)
{
int i;
um_app_end();
}
/* The midi interpreter calls this function when it completely parses a */
/* channel message */
/* You can use this function to keep track of all midi messages that are */
/* being sent to ultramid, and to make sure patches are loaded before */
/* a program change occurs */
#pragma warn -par
void chanmessage(int status,int c1,int c2)
{
Page #4
FILE: UMUART.C
int chan = status & 0xf;
switch ( status & 0xf0 ) {
case note_off:
break;
case note_on:
if (chan == 9) {
if (um_load_patch(c1+128)) {
printf("couldn't load patch %d\n", c1+128);
}
}
break;
case poly_aftertouch:
break;
case control_change:
break;
case pitch_wheel:
break;
case program_chng:
if (chan != 9) {
if (um_load_patch(c1)) {
printf("couldn't load patch %d\n", c1);
}
}
break;
case channel_aftertouch:
break;
}
return;
}
#pragma warn .par
static enum { NORMAL, INCHAN1, INCHAN2, INSYSEX, INSYS1, INSYS2 } midi_mode;
/* This array is indexed by the high half of a status byte. It's */
/* value is either the number of bytes needed (1 or 2) for a channel */
/* message, or 0 (meaning it's not a channel message). */
static int chantype[] = {
0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 through 0x70 */
2, 2, 2, 2, 1, 1, 2, 0 /* 0x80 through 0xf0 */
};
/* This function interprets midi data received from the UART. */
void midi_out(int c)
{
static int c1, c2;
static int needed;
static int status = note_on; /* status value (e.g. 0x90==note-on) */
static int running = 0; /* 1 when running status used */
Page #5
FILE: UMUART.C
if (c >= 0xf8 && c <= 0xff) { /* real time message */
return;
}
if (midi_mode != INSYSEX) {
if (c & 0x80) { /* new status byte */
status = c;
running = 0;
midi_mode = NORMAL;
} else {
running = 1;
}
}
switch (midi_mode) {
case NORMAL:
needed = chantype[(status>>4) & 0xf];
if (needed) { /* is it a channel message? */
if (running) {
c1 = c;
if (needed == 1) {
chanmessage( status, c1, 0 );
midi_mode = NORMAL;
break;
} else {
midi_mode = INCHAN2;
break;
}
} else {
midi_mode = INCHAN1;
break;
}
} else {
switch (status) {
case 0xf0: /* start of system exclusive */
midi_mode = INSYSEX;
break;
case 0xf1: /* midi time code quarter frame */
case 0xf3: /* song select */
midi_mode = INSYS1;
break;
case 0xf2: /* song position pointer */
midi_mode = INSYS2;
break;
case 0xf4: /* undefined */
case 0xf5:
case 0xf6: /* tune request */
case 0xf7: /* End of system exclusive */
midi_mode = NORMAL;
Page #6
FILE: UMUART.C
break;
}
if (running) {
midi_out(c);
}
}
return;
case INCHAN1:
c1 = c;
if (needed == 1) {
chanmessage( status, c1, 0 );
midi_mode = NORMAL;
break;
} else {
midi_mode = INCHAN2;
}
break;
case INCHAN2:
running = 0;
c2 = c;
chanmessage( status, c1, c2 );
midi_mode = NORMAL;
break;
case INSYSEX:
if (c == 0xf7) midi_mode = NORMAL;
break;
case INSYS1:
midi_mode = NORMAL;
break;
case INSYS2:
midi_mode = INSYS1;
break;
default:
midi_mode = NORMAL;
}
return;
}
#define MIDI_BUFF 512
unsigned char midi_data[MIDI_BUFF];
int midi_in_index=0;
int midi_out_index=0;
int midi_error = 0;
/* like the library function kbhit(), but for midi data */
int midi_hit(void)
{
if (midi_error) {
printf("midi data error\n");
Page #7
FILE: UMUART.C
um_all_notes_off();
midi_error = 0;
}
return (midi_in_index != midi_out_index);
}
/* get a character from the MIDI FIFO -- foreground function */
int midi_getc(void)
{
int c;
asm pushf;
disable();
c = midi_data[midi_out_index];
midi_out_index = (midi_out_index + 1) % MIDI_BUFF;
asm popf;
return(c);
}
/* get a character from the MIDI UART -- called from an interrupt handler */
/* be careful not to make any DOS calls or library calls from this function. */
/* Also, don't make any calls to UltraMID from this function */
int far midi_receive(int midi_status, int data)
{
int next_index;
asm pushf;
disable();
asm push ds
asm mov ax, DGROUP
asm mov ds, ax
if (midi_status & (UM_ERR_FRAMING|UM_ERR_OVERRUN)) {
error:
midi_error = 1;
midi_in_index = 0;
midi_out_index = 0;
} else {
next_index = (midi_in_index + 1) % MIDI_BUFF;
if (midi_out_index == next_index) {
/* buffer full */
goto error;
} else {
midi_data[midi_in_index] = data;
midi_in_index = next_index;
}
}
asm pop ds
asm popf
return(1);
Page #8
FILE: UMUART.C
}
void help( void )
{
printf("Press 'c' to clear all loaded patches\n");
printf("Press ESC to exit program\n");
}
void unload_patches(void)
{
int i;
for (i=0; i < 255; i++) {
um_unload_patch(i);
}
}
#pragma warn -par
int main(int argc, char *argv[])
{
int c;
ctrlbrk(cbreak);
if (um_link()) exit(1);
if (um_set_midi_callback(midi_receive)) {
printf("Couldn't activate the UltraSound UART\n");
um_unlink();
exit(2);
}
user_is_tired = 0;
while (!user_is_tired) {
if (midi_hit()) {
c = midi_getc();
midi_out(c);
um_data_out(c);
}
if (kbhit()) {
c = getch();
switch (c) {
case '?' :
help();
break;
case 'c' :
unload_patches();
break;
case 27:
user_is_tired = 1;
break;
}
Page #9
FILE: UMUART.C
}
}
unload_patches();
um_clear_midi_callback();
reset_um(); /* reset ultramid and turn off all playing notes */
um_unlink();
return(0);
}
#pragma warn .par
Page #10